1 /*
2 * Copyright (C) 2011 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.google.common.cache;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20
21 import com.google.common.annotations.Beta;
22 import com.google.common.annotations.GwtCompatible;
23 import com.google.common.base.MoreObjects;
24 import com.google.common.base.Objects;
25
26 import java.util.concurrent.Callable;
27
28 import javax.annotation.Nullable;
29
30 /**
31 * Statistics about the performance of a {@link Cache}. Instances of this class are immutable.
32 *
33 * <p>Cache statistics are incremented according to the following rules:
34 *
35 * <ul>
36 * <li>When a cache lookup encounters an existing cache entry {@code hitCount} is incremented.
37 * <li>When a cache lookup first encounters a missing cache entry, a new entry is loaded.
38 * <ul>
39 * <li>After successfully loading an entry {@code missCount} and {@code loadSuccessCount} are
40 * incremented, and the total loading time, in nanoseconds, is added to
41 * {@code totalLoadTime}.
42 * <li>When an exception is thrown while loading an entry, {@code missCount} and {@code
43 * loadExceptionCount} are incremented, and the total loading time, in nanoseconds, is
44 * added to {@code totalLoadTime}.
45 * <li>Cache lookups that encounter a missing cache entry that is still loading will wait
46 * for loading to complete (whether successful or not) and then increment {@code missCount}.
47 * </ul>
48 * <li>When an entry is evicted from the cache, {@code evictionCount} is incremented.
49 * <li>No stats are modified when a cache entry is invalidated or manually removed.
50 * <li>No stats are modified on a query to {@link Cache#getIfPresent}.
51 * <li>No stats are modified by operations invoked on the {@linkplain Cache#asMap asMap} view of
52 * the cache.
53 * </ul>
54 *
55 * <p>A lookup is specifically defined as an invocation of one of the methods
56 * {@link LoadingCache#get(Object)}, {@link LoadingCache#getUnchecked(Object)},
57 * {@link Cache#get(Object, Callable)}, or {@link LoadingCache#getAll(Iterable)}.
58 *
59 * @author Charles Fry
60 * @since 10.0
61 */
62 @Beta
63 @GwtCompatible
64 public final class CacheStats {
65 private final long hitCount;
66 private final long missCount;
67 private final long loadSuccessCount;
68 private final long loadExceptionCount;
69 private final long totalLoadTime;
70 private final long evictionCount;
71
72 /**
73 * Constructs a new {@code CacheStats} instance.
74 *
75 * <p>Five parameters of the same type in a row is a bad thing, but this class is not constructed
76 * by end users and is too fine-grained for a builder.
77 */
78 public CacheStats(long hitCount, long missCount, long loadSuccessCount,
79 long loadExceptionCount, long totalLoadTime, long evictionCount) {
80 checkArgument(hitCount >= 0);
81 checkArgument(missCount >= 0);
82 checkArgument(loadSuccessCount >= 0);
83 checkArgument(loadExceptionCount >= 0);
84 checkArgument(totalLoadTime >= 0);
85 checkArgument(evictionCount >= 0);
86
87 this.hitCount = hitCount;
88 this.missCount = missCount;
89 this.loadSuccessCount = loadSuccessCount;
90 this.loadExceptionCount = loadExceptionCount;
91 this.totalLoadTime = totalLoadTime;
92 this.evictionCount = evictionCount;
93 }
94
95 /**
96 * Returns the number of times {@link Cache} lookup methods have returned either a cached or
97 * uncached value. This is defined as {@code hitCount + missCount}.
98 */
99 public long requestCount() {
100 return hitCount + missCount;
101 }
102
103 /**
104 * Returns the number of times {@link Cache} lookup methods have returned a cached value.
105 */
106 public long hitCount() {
107 return hitCount;
108 }
109
110 /**
111 * Returns the ratio of cache requests which were hits. This is defined as
112 * {@code hitCount / requestCount}, or {@code 1.0} when {@code requestCount == 0}.
113 * Note that {@code hitRate + missRate =~ 1.0}.
114 */
115 public double hitRate() {
116 long requestCount = requestCount();
117 return (requestCount == 0) ? 1.0 : (double) hitCount / requestCount;
118 }
119
120 /**
121 * Returns the number of times {@link Cache} lookup methods have returned an uncached (newly
122 * loaded) value, or null. Multiple concurrent calls to {@link Cache} lookup methods on an absent
123 * value can result in multiple misses, all returning the results of a single cache load
124 * operation.
125 */
126 public long missCount() {
127 return missCount;
128 }
129
130 /**
131 * Returns the ratio of cache requests which were misses. This is defined as
132 * {@code missCount / requestCount}, or {@code 0.0} when {@code requestCount == 0}.
133 * Note that {@code hitRate + missRate =~ 1.0}. Cache misses include all requests which
134 * weren't cache hits, including requests which resulted in either successful or failed loading
135 * attempts, and requests which waited for other threads to finish loading. It is thus the case
136 * that {@code missCount >= loadSuccessCount + loadExceptionCount}. Multiple
137 * concurrent misses for the same key will result in a single load operation.
138 */
139 public double missRate() {
140 long requestCount = requestCount();
141 return (requestCount == 0) ? 0.0 : (double) missCount / requestCount;
142 }
143
144 /**
145 * Returns the total number of times that {@link Cache} lookup methods attempted to load new
146 * values. This includes both successful load operations, as well as those that threw
147 * exceptions. This is defined as {@code loadSuccessCount + loadExceptionCount}.
148 */
149 public long loadCount() {
150 return loadSuccessCount + loadExceptionCount;
151 }
152
153 /**
154 * Returns the number of times {@link Cache} lookup methods have successfully loaded a new value.
155 * This is always incremented in conjunction with {@link #missCount}, though {@code missCount}
156 * is also incremented when an exception is encountered during cache loading (see
157 * {@link #loadExceptionCount}). Multiple concurrent misses for the same key will result in a
158 * single load operation.
159 */
160 public long loadSuccessCount() {
161 return loadSuccessCount;
162 }
163
164 /**
165 * Returns the number of times {@link Cache} lookup methods threw an exception while loading a
166 * new value. This is always incremented in conjunction with {@code missCount}, though
167 * {@code missCount} is also incremented when cache loading completes successfully (see
168 * {@link #loadSuccessCount}). Multiple concurrent misses for the same key will result in a
169 * single load operation.
170 */
171 public long loadExceptionCount() {
172 return loadExceptionCount;
173 }
174
175 /**
176 * Returns the ratio of cache loading attempts which threw exceptions. This is defined as
177 * {@code loadExceptionCount / (loadSuccessCount + loadExceptionCount)}, or
178 * {@code 0.0} when {@code loadSuccessCount + loadExceptionCount == 0}.
179 */
180 public double loadExceptionRate() {
181 long totalLoadCount = loadSuccessCount + loadExceptionCount;
182 return (totalLoadCount == 0)
183 ? 0.0
184 : (double) loadExceptionCount / totalLoadCount;
185 }
186
187 /**
188 * Returns the total number of nanoseconds the cache has spent loading new values. This can be
189 * used to calculate the miss penalty. This value is increased every time
190 * {@code loadSuccessCount} or {@code loadExceptionCount} is incremented.
191 */
192 public long totalLoadTime() {
193 return totalLoadTime;
194 }
195
196 /**
197 * Returns the average time spent loading new values. This is defined as
198 * {@code totalLoadTime / (loadSuccessCount + loadExceptionCount)}.
199 */
200 public double averageLoadPenalty() {
201 long totalLoadCount = loadSuccessCount + loadExceptionCount;
202 return (totalLoadCount == 0)
203 ? 0.0
204 : (double) totalLoadTime / totalLoadCount;
205 }
206
207 /**
208 * Returns the number of times an entry has been evicted. This count does not include manual
209 * {@linkplain Cache#invalidate invalidations}.
210 */
211 public long evictionCount() {
212 return evictionCount;
213 }
214
215 /**
216 * Returns a new {@code CacheStats} representing the difference between this {@code CacheStats}
217 * and {@code other}. Negative values, which aren't supported by {@code CacheStats} will be
218 * rounded up to zero.
219 */
220 public CacheStats minus(CacheStats other) {
221 return new CacheStats(
222 Math.max(0, hitCount - other.hitCount),
223 Math.max(0, missCount - other.missCount),
224 Math.max(0, loadSuccessCount - other.loadSuccessCount),
225 Math.max(0, loadExceptionCount - other.loadExceptionCount),
226 Math.max(0, totalLoadTime - other.totalLoadTime),
227 Math.max(0, evictionCount - other.evictionCount));
228 }
229
230 /**
231 * Returns a new {@code CacheStats} representing the sum of this {@code CacheStats}
232 * and {@code other}.
233 *
234 * @since 11.0
235 */
236 public CacheStats plus(CacheStats other) {
237 return new CacheStats(
238 hitCount + other.hitCount,
239 missCount + other.missCount,
240 loadSuccessCount + other.loadSuccessCount,
241 loadExceptionCount + other.loadExceptionCount,
242 totalLoadTime + other.totalLoadTime,
243 evictionCount + other.evictionCount);
244 }
245
246 @Override
247 public int hashCode() {
248 return Objects.hashCode(hitCount, missCount, loadSuccessCount, loadExceptionCount,
249 totalLoadTime, evictionCount);
250 }
251
252 @Override
253 public boolean equals(@Nullable Object object) {
254 if (object instanceof CacheStats) {
255 CacheStats other = (CacheStats) object;
256 return hitCount == other.hitCount
257 && missCount == other.missCount
258 && loadSuccessCount == other.loadSuccessCount
259 && loadExceptionCount == other.loadExceptionCount
260 && totalLoadTime == other.totalLoadTime
261 && evictionCount == other.evictionCount;
262 }
263 return false;
264 }
265
266 @Override
267 public String toString() {
268 return MoreObjects.toStringHelper(this)
269 .add("hitCount", hitCount)
270 .add("missCount", missCount)
271 .add("loadSuccessCount", loadSuccessCount)
272 .add("loadExceptionCount", loadExceptionCount)
273 .add("totalLoadTime", totalLoadTime)
274 .add("evictionCount", evictionCount)
275 .toString();
276 }
277 }